Gopher Proxy
gopher.conman.org:70 gopher.conman.org:70/0Boston:Src:src/wbtum.c
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/*********************************************************************
*
* Copyright 2015 by Sean Conner. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Comments, questions and criticisms can be sent to: sean@conman.org
*
**********************************************************************/
#include <stddef.h>
#include <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "wbtum.h"
/************************************************************************/
struct value
{
char const *txt;
ptrdiff_t len;
int val;
};
/************************************************************************/
static bool parse_num(
struct value *pv,
char const **p,
int low,
int high
)
{
long val;
assert(pv != NULL);
assert(p != NULL);
assert(*p != NULL);
assert(low <= high);
if (!isdigit(**p))
return false;
pv->txt = *p;
errno = 0;
val = strtol(*p,(char **)p,10);
if (errno != 0)
return false;
if (val < (long)low)
return false;
if (val > (long)high)
return false;
assert(val >= (long)INT_MIN);
assert(val <= (long)INT_MAX);
pv->val = (int)val;
pv->len = *p - pv->txt;
return true;
}
/************************************************************************/
static bool check_dates(
tumbler__s const *tum,
struct btm const *restrict first,
struct btm const *restrict last
)
{
bool start_okay = false;
bool stop_okay = false;
assert(tum != NULL);
assert(first != NULL);
assert(last != NULL);
switch(tum->ustart)
{
case UNIT_YEAR:
start_okay = (btm_cmp_year(&tum->start,first) >= 0)
&& (btm_cmp_year(&tum->start,last) <= 0);
break;
case UNIT_MONTH:
start_okay = (btm_cmp_month(&tum->start,first) >= 0)
&& (btm_cmp_month(&tum->start,last) <= 0);
break;
case UNIT_DAY:
start_okay = (btm_cmp_date(&tum->start,first) >= 0)
&& (btm_cmp_date(&tum->start,last) <= 0);
break;
case UNIT_PART:
start_okay = (btm_cmp(&tum->start,first) >= 0)
&& (btm_cmp(&tum->start,last) <= 0);
break;
}
switch(tum->ustop)
{
case UNIT_YEAR:
stop_okay = (btm_cmp_year(&tum->stop,first) >= 0)
&& (btm_cmp_year(&tum->stop,last) <= 0);
break;
case UNIT_MONTH:
stop_okay = (btm_cmp_month(&tum->stop,first) >= 0)
&& (btm_cmp_month(&tum->stop,last) <= 0);
break;
case UNIT_DAY:
stop_okay = (btm_cmp_date(&tum->stop,first) >= 0)
&& (btm_cmp_date(&tum->stop,last) <= 0);
break;
case UNIT_PART:
stop_okay = (btm_cmp(&tum->stop,first) >= 0)
&& (btm_cmp(&tum->stop,last) <= 0);
break;
}
return start_okay && stop_okay;
}
/************************************************************************/
bool tumbler_new(
tumbler__s *tum,
char const *text,
struct btm const *restrict first,
struct btm const *restrict last
)
{
/*----------------------------------------------------------------------
; all variables are defined here, even though u2, u3, u4, segments and
; part aren't used until tumbler_new_range. It appears that defining them
; there causes a compiler error. It's either a bug in the version of GCC
; I'm using, or you can't declare variables after a goto target label.
;
; I'm not sure what the case is here, so I'm moving them up here.
;----------------------------------------------------------------------*/
struct value u1;
struct value u2;
struct value u3;
struct value u4;
bool part = false;
assert(tum != NULL);
assert(text != NULL);
assert(first != NULL);
assert(last != NULL);
memset(tum,0,sizeof(tumbler__s));
memset(&u1,0,sizeof(u1));
memset(&u2,0,sizeof(u2));
memset(&u3,0,sizeof(u3));
memset(&u4,0,sizeof(u4));
/*-------------------------------------------------
; parse year
;-------------------------------------------------*/
if (!parse_num(&u1,&text,first->year,last->year))
return false;
if (u1.val == first->year)
{
tum->start.month = first->month;
tum->start.day = first->day;
}
else
{
tum->start.month = 1;
tum->start.day = 1;
}
tum->ustart = tum->ustop = UNIT_YEAR;
tum->start.year = tum->stop.year = u1.val;
tum->start.part = 1;
tum->stop.month = 12;
tum->stop.day = 31;
tum->stop.part = ENTRY_MAX;
if (*text == '\0')
return check_dates(tum,first,last);
if (*text == '-')
goto tumbler_new_range;
if (*text != '/')
return false;
text++;
if (*text == '\0')
return tum->redirect |= true;
/*-------------------------------------------------
; parse month
;-------------------------------------------------*/
if (!parse_num(&u1,&text,1,12))
return false;
assert(tum->start.part == 1);
assert(tum->stop.part == ENTRY_MAX);
tum->start.month = tum->stop.month = u1.val;
tum->ustart = tum->ustop = UNIT_MONTH;
tum->stop.day = max_monthday(tum->start.year,tum->start.month);
if (u1.len == 1)
tum->redirect |= true;
if (*text == '\0')
return check_dates(tum,first,last);
if (*text == '-')
goto tumbler_new_range;
if (*text != '/')
return false;
text++;
if (*text == '\0')
return tum->redirect |= true;
/*--------------------
; parse day
;---------------------*/
if (!parse_num(&u1,&text,1,max_monthday(tum->start.year,tum->start.month)))
return false;
tum->start.day = tum->stop.day = u1.val;
tum->ustart = tum->ustop = UNIT_DAY;
if (u1.len == 1)
tum->redirect |= true;
if (*text == '\0')
return check_dates(tum,first,last);
if (*text == '-')
goto tumbler_new_range;
if (*text == '/')
{
if (text[1] == '\0')
return tum->redirect |= true;
else
goto tumbler_new_file;
}
if (*text != '.')
return false;
text++;
if (*text == '\0')
return false;
/*-----------------------------
; parse part
;-----------------------------*/
if (!parse_num(&u1,&text,1,ENTRY_MAX))
return false;
tum->start.part = tum->stop.part = u1.val;
tum->ustart = tum->ustop = UNIT_PART;
if ((u1.len > 1) && (*u1.txt == '0'))
tum->redirect |= true;
if (*text == '\0')
return check_dates(tum,first,last);
if (*text != '-')
return false;
assert(*text == '-');
goto tumbler_new_range;
/*-----------------------------
; parse file
;-----------------------------*/
tumbler_new_file:
assert(*text == '/');
tum->file = true;
text++;
for (size_t i = 0 ; i < FILENAME_MAX ; i++)
{
tum->filename[i] = *text++;
if (tum->filename[i] == '\0')
return check_dates(tum,first,last);
if (tum->filename[i] == '/')
return false;
}
return false;
/*------------------------------------------------------------------------
; parse the range portion of the tumbler. Since the number of segments in
; this portion is variable, we parse first without reguard to the actual
; ranges, keep track of the number of segments found, then after we're
; done with the parsing, we figure everything out.
;-------------------------------------------------------------------------*/
tumbler_new_range:
assert(*text == '-');
tum->range = true;
text++;
/*--------------------
; first unit
;---------------------*/
if (!parse_num(&u1,&text,1,INT_MAX))
return false;
tum->segments++;
if (*text == '\0')
goto tumbler_new_calculate;
if (*text == '.')
goto tumbler_new_range_part;
if (*text != '/')
return false;
text++;
if (*text == '\0')
{
tum->redirect |= true;
goto tumbler_new_calculate;
}
/*------------------------------
; second unit
;------------------------------*/
if (!parse_num(&u2,&text,1,INT_MAX))
return false;
tum->segments++;
if (*text == '\0')
goto tumbler_new_calculate;
if (*text == '.')
goto tumbler_new_range_part;
if (*text != '/')
return false;
text++;
if (*text == '\0')
{
tum->redirect |= true;
goto tumbler_new_calculate;
}
/*----------------------------------
; third unit
;-----------------------------------*/
if (!parse_num(&u3,&text,1,INT_MAX))
return false;
tum->segments++;
if (*text == '\0')
goto tumbler_new_calculate;
if (*text != '.')
return false;
/*-----------------------------------------------------------------------
; the fourth unit, OR the part. The part will always be stuffed into u4.
;------------------------------------------------------------------------*/
tumbler_new_range_part:
assert(part == false);
assert(*text == '.');
assert(tum->segments >= 1);
text++;
if (!parse_num(&u4,&text,1,ENTRY_MAX))
return false;
tum->segments++;
part = true;
if ((u4.len > 1) && (*u4.txt == '0'))
tum->redirect |= true;
if (*text != '\0')
return false;
/*----------------------------------------------------------------------
; Now figure everything out. If we have all four segments, then this part
; is easy---we fill out every part of the range portion with our values.
; All values have already been checked against the lower bound of 1, so we
; only have to check against the upper limit for the most part.
;
; Here is also were we check for leading or missing zeros, and set the
; redirect flag appropriately. The month and day require a leading zero,
; while the part does not.
;------------------------------------------------------------------------*/
tumbler_new_calculate:
if (tum->segments == 4)
{
tum->redirect |= (u2.len == 1)
|| (u3.len == 1)
|| ((u4.len > 1) && (*u4.txt == '0'))
;
tum->stop.year = u1.val;
tum->stop.month = u2.val;
tum->stop.day = u3.val;
tum->stop.part = u4.val;
tum->ustop = UNIT_PART;
return check_dates(tum,first,last);
}
/*---------------------------------------------------------------
; If we have three segments, there are two cases to consider:
;
; 2000/02/03-2004/05/06
;
; or
;
; 2000/02/03-04/05.6
;
; The first is when part is false. This is a fairly easy case to handle.
;-----------------------------------------------------------------------*/
else if (tum->segments == 3)
{
if (part)
{
tum->redirect |= (u1.len == 1)
|| (u2.len == 1)
|| ((u4.len > 1) && (*u4.txt == '0'))
;
tum->stop.month = u1.val;
tum->stop.day = u2.val;
tum->stop.part = u4.val;
tum->ustop = UNIT_PART;
return check_dates(tum,first,last);
}
else
{
tum->stop.year = u1.val;
tum->stop.month = u2.val;
tum->stop.day = u3.val;
tum->stop.part = ENTRY_MAX;
return check_dates(tum,first,last);
}
}
/*------------------------------------------------------------------
; We have two segments. If there is a part, then it's easy:
;
; 2000/02/03-04.1
;
; The next few cases not so:
;
; 2000/02/03-2004/05
; 2000/02/03-04/05
;
; We use some huristics to distinquish between the two. This means we
; can't specify years before 13 AD, but I doubt that will be an issue any
; time soon.
;------------------------------------------------------------------------*/
else if (tum->segments == 2)
{
if (part)
{
tum->redirect |= (u1.len == 1)
|| ((u4.len > 1) && (*u4.txt == '0'))
;
tum->stop.day = u1.val;
tum->stop.part = u4.val;
tum->ustop = UNIT_PART;
return check_dates(tum,first,last);
}
else
{
/*---------------------------------------
; check for year/month vs. month/day
;--------------------------------------*/
if (u1.val >= first->year)
{
tum->redirect |= (u2.len == 1);
tum->stop.year = u1.val;
tum->stop.month = u2.val;
tum->stop.day = max_monthday(tum->stop.year,tum->stop.month);
tum->stop.part = ENTRY_MAX;
tum->ustop = UNIT_MONTH;
return check_dates(tum,first,last);
}
else
{
tum->redirect |= (u1.len == 1)
|| (u2.len == 1)
;
tum->stop.month = u1.val;
tum->stop.day = u2.val;
tum->stop.part = ENTRY_MAX;
tum->ustop = UNIT_DAY;
return check_dates(tum,first,last);
}
}
}
/*------------------------------------------------------------------
; Final case---one segment. We use the unit defined in the starting
; portion to determine what type of segment this is.
;-------------------------------------------------------------------*/
else
{
switch(tum->ustart)
{
case UNIT_YEAR:
tum->stop.year = u1.val;
tum->stop.month = 12;
tum->stop.day = 31; /* there are always 31 days in Debtember */
tum->stop.part = ENTRY_MAX;
tum->ustop = UNIT_YEAR;
return check_dates(tum,first,last);
case UNIT_MONTH:
if (u1.val > 12) return false;
tum->redirect |= (u1.len == 1);
tum->stop.month = u1.val;
tum->stop.day = max_monthday(tum->stop.year,tum->stop.month);
tum->stop.part = ENTRY_MAX;
tum->ustop = UNIT_MONTH;
return check_dates(tum,first,last);
case UNIT_DAY:
if (u1.val > 31) return false;
tum->redirect |= (u1.len == 1);
tum->stop.day = u1.val;
tum->stop.part = ENTRY_MAX;
tum->ustop = UNIT_DAY;
return check_dates(tum,first,last);
case UNIT_PART:
if (u1.val > ENTRY_MAX) return false;
tum->redirect |= ((u1.len > 1) && (*u1.txt == '0'));
tum->stop.part = u1.val;
tum->ustop = UNIT_PART;
return check_dates(tum,first,last);
}
}
return false;
}
/************************************************************************/
char *tumbler_canonical(tumbler__s const *tum)
{
char start[32];
char stop [32];
char *ret;
int rc;
assert(tum != NULL);
if (tum->start.year == 0)
return strdup("");
if (tum->file)
{
rc = asprintf(
&ret,
"%d/%02d/%02d/%s",
tum->start.year,
tum->start.month,
tum->start.day,
tum->filename
);
if (rc == -1)
return NULL;
else
return ret;
}
switch(tum->ustart)
{
case UNIT_YEAR:
snprintf(start,sizeof(start),"%d",tum->start.year);
break;
case UNIT_MONTH:
snprintf(start,sizeof(start),"%d/%02d",tum->start.year,tum->start.month);
break;
case UNIT_DAY:
snprintf(start,sizeof(start),"%d/%02d/%02d",tum->start.year,tum->start.month,tum->start.day);
break;
case UNIT_PART:
snprintf(start,sizeof(start),"%d/%02d/%02d.%d",tum->start.year,tum->start.month,tum->start.day,tum->start.part);
break;
}
if (!tum->range)
return strdup(start);
if (tum->segments == 4)
snprintf(stop,sizeof(stop),"%d/%02d/%02d.%d",tum->stop.year,tum->stop.month,tum->stop.day,tum->stop.part);
else if (tum->segments == 3)
{
if (tum->ustop == UNIT_PART)
snprintf(stop,sizeof(stop),"%02d/%02d.%d",tum->stop.month,tum->stop.day,tum->stop.part);
else
snprintf(stop,sizeof(stop),"%d/%02d/%02d",tum->stop.year,tum->stop.month,tum->stop.day);
}
else if (tum->segments == 2)
{
switch(tum->ustop)
{
/*-------------------------------------------------------------------
; with two segments, we ended up on either a MONTH, DAY or PART.
; There is no way we could end up with a YEAR unless there was a
; problem with the code. Also, there is no way we could get an INDEX
; here, again, unless there was a problem with the code.
;--------------------------------------------------------------------*/
case UNIT_YEAR:
assert(0);
return NULL;
case UNIT_MONTH:
snprintf(stop,sizeof(stop),"%d/%02d",tum->stop.year,tum->stop.month);
break;
case UNIT_DAY:
snprintf(stop,sizeof(stop),"%02d/%02d",tum->stop.month,tum->stop.day);
break;
case UNIT_PART:
snprintf(stop,sizeof(stop),"%02d.%d",tum->stop.day,tum->stop.part);
break;
}
}
else
{
switch(tum->ustop)
{
case UNIT_YEAR:
snprintf(stop,sizeof(stop),"%d",tum->stop.year);
break;
case UNIT_MONTH:
snprintf(stop,sizeof(stop),"%02d",tum->stop.month);
break;
case UNIT_DAY:
snprintf(stop,sizeof(stop),"%02d",tum->stop.day);
break;
case UNIT_PART:
snprintf(stop,sizeof(stop),"%d",tum->stop.part);
break;
}
}
rc = asprintf(&ret,"%s-%s",start,stop);
if (rc == -1)
return NULL;
else
return ret;
}
/************************************************************************/
bool thisday_new(tumbler__s *tum,char const *text)
{
struct value month;
struct value day;
assert(tum != NULL);
assert(text != NULL);
memset(&month,0,sizeof(month));
memset(&day, 0,sizeof(day));
if (!parse_num(&month,&text,1,12))
return false;
if (*text != '/')
return false;
tum->start.month = month.val;
text++;
if (!parse_num(&day,&text,1,31))
return false;
if (*text != '\0')
return false;
tum->start.day = day.val;
tum->redirect = (month.len == 1) | (day.len == 1);
tum->file = false;
tum->range = false;
tum->ustart = UNIT_PART;
tum->ustop = UNIT_PART;
tum->segments = 0;
tum->filename[0] = '\0';
return true;
}
/************************************************************************/
.
you're on gopher.conman.org^70/0Boston:Src:src/wbtum.c
back link is gopher.conman.org:70/1Boston:Src:src/